﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;

namespace SpectationClient.SpectralTools {
    public class SPECTATION_BinaryReader : BinaryReader{

        private long offset = 0;
        private byte sourceTypeSize = 1;
        private ENVI_FileReader.IDLDataType idlDataType;

        #region Constructors
        public SPECTATION_BinaryReader(Stream input, ENVI_FileReader.IDLDataType idlDataType)
            : base(input) {
             init(0, idlDataType);
        }

        public SPECTATION_BinaryReader(Byte[] input, ENVI_FileReader.IDLDataType idlDataType)
            : base(new MemoryStream(input, false)) {
                init(0, idlDataType);
        }
        public SPECTATION_BinaryReader(FileInfo input, ENVI_FileReader.IDLDataType idlDataType)
            : base(input.OpenRead()) {
                init(0, idlDataType);
        }

        public SPECTATION_BinaryReader(Stream input, ENVI_FileReader.IDLDataType idlDataType, long offset):base(input) {
            init(offset, idlDataType);

        }
        public SPECTATION_BinaryReader(Byte[] input, ENVI_FileReader.IDLDataType idlDataType, long offset) : base(new MemoryStream(input, false)) {
            init(offset, idlDataType);

        }
        public SPECTATION_BinaryReader(FileInfo input, ENVI_FileReader.IDLDataType idlDataType, long offset) : base(input.OpenRead()) {
            init(offset, idlDataType);
           
        }

        private void init(long offset, ENVI_FileReader.IDLDataType idlType) {
            this.offset = offset;
            this.idlDataType = idlType;
            this.setSourceDataType(idlType);
           
        }
        #endregion

        public Int64 Length {
            get { return this.BaseStream.Length; }
        }

        public long Position {
            get { return base.BaseStream.Position; }
            set { base.BaseStream.Position = value; } 
        }

        public void setPosition(long iPosition) {
            setPosition(iPosition, false);
        }

        /// <summary>
        /// Sets the BinaryReader to the iPosition's byte (after file start/offset in bytes).
        /// 
        /// </summary>
        /// <param name="iPosition"></param>
        public void setPosition(long iPosition, bool absoluteBytePosition) {
            if(!absoluteBytePosition) {
                this.Position = offset + iPosition * this.sourceTypeSize;
            } else {
                this.Position = iPosition;
            }
        }

        /// <summary>
        /// Sets the 
        /// </summary>
        /// <param name="sourceDataType"></param>
        public void setSourceDataType(ENVI_FileReader.IDLDataType sourceDataType) {
            this.idlDataType = sourceDataType;
            this.sourceTypeSize = ENVI_FileReader.getDataTypeByteSize(sourceDataType);
        }
        /// <summary>
        /// Reads n numbers from underlying data set of type U and converts it to
        /// final type T.
        /// 
        /// The data set type T must be specified before.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="n"></param>
        /// <returns></returns>
        public T[] ReadArray<T>(int n) {
            switch(idlDataType){
                case ENVI_FileReader.IDLDataType.Byte_byte: return ReadAByte<T>(n);
                case ENVI_FileReader.IDLDataType.Int16_int: return ReadAInt16<T>(n);
                case ENVI_FileReader.IDLDataType.Int32_long: return ReadAInt32<T>(n);
                case ENVI_FileReader.IDLDataType.Int64_long64: return ReadAInt64<T>(n);
                case ENVI_FileReader.IDLDataType.UInt16_uint: return ReadAUInt16<T>(n);
                case ENVI_FileReader.IDLDataType.UInt32_ulong: return ReadAUInt32<T>(n);
                case ENVI_FileReader.IDLDataType.UInt64_ulong64: return ReadAUInt64<T>(n);
                case ENVI_FileReader.IDLDataType.Single_float: return ReadASingle<T>(n);
                case ENVI_FileReader.IDLDataType.Double_double: return ReadADouble<T>(n);
                default: throw new NotImplementedException();
            }
        }

        private T[] ReadAByte<T>(int length) {
            T[] a = new T[length];
            Type targetType = typeof(T);
            byte size = sizeof(Byte);
            byte[] ba = this.ReadBytes(length * size);
            for(int i=0; i < length; i++) {
                a[i] = (T)Convert.ChangeType(ba, targetType);
            }
            return a;
        }

        private T[] ReadAInt16<T>(int length) {
            T[] a = new T[length];
            Type targetType = typeof(T);
            byte size = sizeof(Int16);
            byte[] ba = this.ReadBytes(length * size);
            for(int i=0; i < length; i++) {
                a[i] = (T)Convert.ChangeType(BitConverter.ToInt16(ba, i*size), targetType);
            }
            return a;
        }

        private T[] ReadAInt32<T>(int length) {
            T[] a = new T[length];
            Type targetType = typeof(T);
            byte size = sizeof(Int32);
            byte[] ba = this.ReadBytes(length * size);
            for(int i=0; i < length; i++) {
                a[i] = (T)Convert.ChangeType(BitConverter.ToInt32(ba, i*size), targetType);
            }
            return a;
        }

        private T[] ReadAInt64<T>(int length) {
            T[] a = new T[length];
            Type targetType = typeof(T);
            byte size = sizeof(Int64);
            byte[] ba = this.ReadBytes(length * size);
            for(int i=0; i < length; i++) {
                a[i] = (T)Convert.ChangeType(BitConverter.ToInt64(ba, i*size), targetType);
            }
            return a;
        }

        private T[] ReadAUInt16<T>(int length) {
            T[] a = new T[length];
            Type targetType = typeof(T);
            byte size = sizeof(UInt16);
            byte[] ba = this.ReadBytes(length * size);
            for(int i=0; i < length; i++) {
                a[i] = (T)Convert.ChangeType(BitConverter.ToUInt16(ba, i*size), targetType);
            }
            return a;
        }
        private T[] ReadAUInt32<T>(int length) {
            T[] a = new T[length];
            Type targetType = typeof(T);
            byte size = sizeof(UInt32);
            byte[] ba = this.ReadBytes(length * size);
            for(int i=0; i < length; i++) {
                a[i] = (T)Convert.ChangeType(BitConverter.ToUInt32(ba, i*size), targetType);
            }
            return a;
        }
        private T[] ReadAUInt64<T>(int length) {
            T[] a = new T[length];
            Type targetType = typeof(T);
            byte size = sizeof(UInt64);
            byte[] ba = this.ReadBytes(length * size);
            for(int i=0; i < length; i++) {
                a[i] = (T)Convert.ChangeType(BitConverter.ToUInt64(ba, i*size), targetType);
            }
            return a;
        }
        private T[] ReadASingle<T>(int length) {
            T[] a = new T[length];
            Type targetType = typeof(T);
            byte size = sizeof(Single);
            byte[] ba = this.ReadBytes(length * size);
            for(int i=0; i < length; i++) {
                a[i] = (T)Convert.ChangeType(BitConverter.ToSingle(ba, i*size), targetType);
            }
            return a;
        }
        private T[] ReadADouble<T>(int length) {
            T[] a = new T[length];
            Type targetType = typeof(T);
            byte size = sizeof(Double);
            byte[] ba = this.ReadBytes(length * size);
            for(int i=0; i < length; i++) {
                a[i] = (T)Convert.ChangeType(BitConverter.ToDouble(ba, i*size), targetType);
            }
            return a;
        }
        private T[] ReadABoolean<T>(int length) {
            T[] a = new T[length];
            Type targetType = typeof(T);
            byte size = sizeof(Boolean);
            byte[] ba = this.ReadBytes(length * size);
            for(int i=0; i < length; i++) {
                a[i] = (T)Convert.ChangeType(BitConverter.ToBoolean(ba, i*size), targetType);
            }
            return a;
        }
        private T[] ReadAChar<T>(int length) {
            T[] a = new T[length];
            Type targetType = typeof(T);
            byte size = sizeof(Char);
            byte[] ba = this.ReadBytes(length * size);
            for(int i=0; i < length; i++) {
                a[i] = (T)Convert.ChangeType(BitConverter.ToChar(ba, i*size), targetType);
            }
            return a;
        }

        /*
        private Int64[] ReadAInt64(int n) {
            Int64[] a = new Int64[n];
            byte size = sizeof(Int64);
            byte[] ba = this.ReadBytes(n * size);
            for(int i=0; i < n; i++) {
                a[i] = BitConverter.ToInt64(ba, i*size);
            }
            return a;
        }


        public Single[] ReadASingle(int n) {
            Single[] a = new Single[n];
            byte size = sizeof(Single);
            byte[] ba = this.ReadBytes(n * size);
            for(int i=0; i < n; i++) {
                a[i] = BitConverter.ToSingle(ba, i*size);
            }
            return a;
        }
        public Double[] ReadADouble(int n) {
            Double[] a = new Double[n];
            byte size = sizeof(Double);
            byte[] ba = this.ReadBytes(n * size);
            for(int i=0; i < n; i++) {
                a[i] = BitConverter.ToDouble(ba, i*size);
            }
            return a;
        }
        public UInt16[] ReadAUInt16(int n) {
            UInt16[] a = new UInt16[n];
            byte size = sizeof(UInt16);
            byte[] ba = this.ReadBytes(n * size);
            for(int i=0; i < n; i++) {
                a[i] = BitConverter.ToUInt16(ba, i*size);
            }
            return a;
        }
        public UInt32[] ReadAUInt32(int n) {
            UInt32[] a = new UInt32[n];
            byte size = sizeof(UInt32);
            byte[] ba = this.ReadBytes(n * size);
            for(int i=0; i < n; i++) {
                a[i] = BitConverter.ToUInt32(ba, i*size);
            }
            return a;
        }

        public UInt64[] ReadAUInt64(int n) {
            UInt64[] a = new UInt64[n];
            byte size = sizeof(UInt64);
            byte[] ba = this.ReadBytes(n * size);
            for(int i=0; i < n; i++) {
                a[i] = BitConverter.ToUInt64(ba, i*size);
            }
            return a;
        }
        
        public Char[] ReadAChar(int n) {
            Char[] a = new Char[n];
            byte size = sizeof(Char);
            byte[] ba = this.ReadBytes(n * size);
            for(int i=0; i < n; i++) {
                a[i] = BitConverter.ToChar(ba, i*size);
            }
            return a;
        }
        */




        private static Type getType<T>() {
            switch(typeof(T).Name) {
                case "System.Int32": return typeof(Int16);
                case "System.Int16": return typeof(Int32);
                case "System.Int64": return typeof(Int64);
                case "System.Single": return typeof(Single);
                case "System.Double": return typeof(Double);
                case "System.Boolean": return typeof(Boolean);
                case "System.Char": return typeof(Char);
                case "System.UInt16": return typeof(UInt16);
                case "System.UInt32": return typeof(UInt32);
                case "System.UInt64": return typeof(UInt64);
                case "System.Byte": return typeof(Byte);
                default: throw new NotImplementedException();
            }
        }
    }
}
